/**
 * @file object_detect.cc
 * @author your name (you@domain.com)
 * @brief 3号线程，调用目标检测，用于驱使状态机
 * @version 0.1
 * @date 2023-2-02
 * 
 * @copyright Copyright (c) 2022
 * 
 */

#include "yolov5s_detect.h"
#include <unistd.h>
#include <mutex>
#include <sys/time.h>
#include <semaphore.h>

#include <iostream>
#include <queue>
#include <utility>
#include <string>

#include "object_detect.h"          //因为这里编译涉及到struct timval这个数据结构在time.h中，因此需要放它后面

extern "C"{
    #include <signal.h>
    #include "rkmedia_api.h"
    #include "rk_nv12_to_rgb.h"
    #include "rk_list_process.h"
}

extern detect_result_group_t detect_result_group;
extern int g_flag_run;
extern rknn_list *rknn_list_;

extern sem_t bin_sem_audio;  // 在音频处理的线程中有定义
extern RK_CHAR *pInputPath;
struct ObjectDetectBuffer object_detect_buffer = {0,0,0,{0}};         //放置检测结果的buffer数组

//和1号线程传递信息的flag和互斥锁
std::mutex flag_triggle_mutex;        
std::queue<std::pair<std::string, cv::Mat>> queueSaveimg_640;

//添加一个信号处理函数，用于捕获段错误 - 无效内存访问的问题
static void sig_proc(int signo) {
  printf("signal %d\n", signo);
}

//统计buffer中各类别占据的比例，50%为敏感值；输出为三种预警标志
static int cnt_buffer(char *buffer){
    uint8_t cnt_yawn = 0;
    uint8_t cnt_close_eye = 0;
    uint8_t cnt_distracted = 0;
    for(int i = 0; i < 20; i++)
    {   
        if(buffer[i] == 1) cnt_yawn += 1;
        else if(buffer[i] == 2) cnt_close_eye += 1;
        else if(buffer[i] == 3 || buffer[i] == 4) cnt_distracted += 1; 
        else;

        if(cnt_close_eye >= 10)    return 1;
        else if(cnt_yawn >= 10)    return 2;
        else if (cnt_distracted >= 10) return 3; 
    }
    printf("RingBuffer中打哈欠个数:%d, 闭眼个数:%d, 分心驾驶个数:%d\n",cnt_yawn,cnt_close_eye, cnt_distracted);
    return 0;
}

double __get_us(struct timeval t) { return (t.tv_sec * 1000000 + t.tv_usec); }

void *object_detect(void *arg){
    uint8_t cpu_id = 3;
    cpu_set_t mask;
    CPU_ZERO(&mask);
    CPU_SET(cpu_id, &mask);
    printf("Bind face_detector thread to CPU %d\n", cpu_id);

    int ret;
    //初始化yolov5s模型及输入输出
    ret = init_rknn_model();
    if(ret < 0){
    printf("Init model error!");
    }
    init_rknn_inoutput();

    MEDIA_BUFFER buffer = NULL;
    cv::Mat img = cv::Mat(1080, 1920, CV_8UC3);           //使用OpenCV创建一个w * h = 1920 * 1080 的图像，为三通道类型（一通道为一个无符号字节类型）
    struct timeval start_time, stop_time1;
    
    signal(SIGSEGV, sig_proc);
    while (g_flag_run) {
        static int pic_sleepy_id = 0;
        static int pic_distracted_id = 0;
        buffer = RK_MPI_SYS_GetMediaBuffer(RK_ID_VI, 1, -1);            //从1通道取数据
        if (!buffer)  continue;
        int rga_buffer_size = 1920 * 1080 * 3; // nv12 3/2, rgb 3
        unsigned char *rga_buffer = (unsigned char *)malloc(rga_buffer_size);
        nv12_to_rgb24((unsigned char *)RK_MPI_MB_GetPtr(buffer), rga_buffer,1920,1080);
        //自适应缩放
        img.data = rga_buffer;
        cv::Mat img_resize = letterBox(img, 640, 640);

        //送入模型检测 并 将检测结果放入目标检测的RingBuffer
        ret = detect(img_resize.data, &object_detect_buffer);

        if(ret < 0){
            printf("detect error!");
        }
        //将检测结果放入画框队列
        if (detect_result_group.count > 0) {
        rknn_list_push(rknn_list_, getCurrentTimeMsec(), detect_result_group);
        int size = rknn_list_size(rknn_list_);
        if (size >= MAX_RKNN_LIST_NUM)
        rknn_list_drop(rknn_list_);
        }
    
        //判断危险驾驶行为帧占RingBuffer总帧的比例
        char *ring_buffer_buffer = object_detect_buffer.buffer;
        ret = cnt_buffer(ring_buffer_buffer);

        if(ret == 1)
        {
            //重度预警
            printf("重度预警\n");
        }

        else if(ret == 2)
        {
            //中度预警
            printf("中度预警\n");
        }
        else if(ret == 3)
        {
            //分心预警
            printf("分心预警\n");
        }
        else;
        //如果预警，就进行视频帧的保存
        if(ret == 1 || ret == 2 || ret == 3)
        {
            std::string pic_name;
            //和1号后处理线程的关系是：生产者消费者模型。用锁机制保证队列的线程安全
            flag_triggle_mutex.lock();
            if(ret == 1){
                pic_name = "save_img/sleepy_" + std::to_string(pic_sleepy_id) + ".jpg";
                pic_sleepy_id += 1;
                pInputPath = "/root/sleepy_strong.pcm";
            }
            else if(ret == 2)
            {
                pic_name = "save_img/sleepy_" + std::to_string(pic_sleepy_id) + ".jpg";
                pic_sleepy_id += 1;  
                pInputPath = "/root/sleepy_mid.pcm";
            }
            else{
                pic_name = "save_img/distracted_" + std::to_string(pic_distracted_id) + ".jpg";
                pic_distracted_id += 1;
                pInputPath = "/root/distracted.pcm";
            }
            queueSaveimg_640.push(make_pair(pic_name, img_resize));
            flag_triggle_mutex.unlock();

            //信号量提醒语音线程。用二进制信号量，如果预警线程在等待，触发它，若它在工作，应该不会累计？
            sem_post(&bin_sem_audio);

        }

        RK_MPI_MB_ReleaseBuffer(buffer);
        if (rga_buffer)
            free(rga_buffer);
    }
}